package cms.api.framework.search;

import cms.api.framework.component.IPluginAware;
import cms.api.framework.component.IService;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.function.Supplier;

/**
 * 索引服务
 *
 * @author kuangjz
 *
 */
public interface IndexService extends IService, IPluginAware {
    /**
     * 缺省的文档ID字段名称
     */
    String DEFAULT_DOCUMENT_ID_FIELD_NAME="id";

    /**
     * 创建索引库
     *
     * @param index
     *        索引库名称
     * @return
     *        true=成功或false=失败
     */
    boolean createIndex(String index);

    /**
     * 删除索引库
     *
     * @param index 索引库名称
     * @return  true=成功或false=失败
     */
    boolean deleteIndex(String index);

    /**
     * 判断索引库是否存在
     *
     * @param index 索引库名称
     * @return true=存在或 false=不存在
     */
    boolean isIndexExist(String index);

    /**
     * 判断索引库中的文档类型是否已存在
     *
     * @param index 索引库名称
     * @param type 文档类型名称
     * @return true=存在或 false=不存在
     */
    boolean isTypeExist(String index, String type);

    /**
     * 为文档类型配置文档ID生成器
     * <pre>
     * 文档生成器用于为文档数据生成唯一ID。文档ID生成器查找规则如下：
     *  1. 如果文档类型未配置文档ID生成器，则使用索引库的文档ID生成器
     *  2. 如果索引库也未配置文档ID生成器，则使用全局配置的文档ID生成器
     * </pre>
     *
     * @param index 索引库名称
     * @param type 文档类型名称
     * @param supplier 文档ID生成器
     */
    void configDocIdGenerator4Type(String index, String type, Supplier<String> supplier);

    /**
     * 为索引库配置文档ID生成器
     *
     * @param index 索引库名称
     * @param supplier 文档ID生成器
     */
    void configDocIdGenertor4Index(String index, Supplier<String> supplier);

    /**
     * 配置全局文档ID生成器
     *
     * @param supplier 文档生成器
     */
    void configGlobalDocIdGenerator(Supplier<String> supplier);

    /**
     * 往索引库里添加文档数据,如果该文档数据中含有ID字段值，当索引库中已经存在该ID的文档数据时，添加操作失败！
     *
     * @param index 索引库名称
     * @param type 文档类型名称
     * @param document 文档数据
     * @return 文档数据ID
     */
    String addDocument(String index, String type, Indexable document);

    /**
     * 往索引库里添加多个文档数据
     *
     * @param index 索引库名称
     * @param type 文档类型名称
     * @param documents 文档数据集
     * @return 文档数据ID集
     */
    default List<String> addDocuments(String index, String type, Iterable<Indexable> documents){
        List<String> result = new ArrayList<>(16);
        for (Indexable document :
                documents) {
            result.add(this.addDocument(index, type, document));
        }
        return result;
    }

    /**
     * 从索引库中删除文档数据
     *
     * @param index 索引库名称
     * @param type 文档类型名称
     * @param id 要删除的文档数据ID
     * @return 被删除的文档数目
     */
    long deleteDocumentById(String index, String type, String id);

    /**
     * 从索引库中删除查询到的文档数据
     *
     * @param index 索引库名称
     * @param type 文档类型名称
     * @param queryParams 查询参数对象
     * @return 被删除的文档数目
     */
    long deleteDocumentByQuery(String index, String type, QueryParams queryParams);

    /**
     * 从索引库中删除多个文档数据
     *
     * @param index 索引库名称
     * @param type 文档类型名称
     * @param ids 要删除的文档ID集字符串，ID之间以逗号分隔
     * @return 被删除的文档数目
     */
    default long deleteDocumentByIds(String index, String type, String ids){
        return this.deleteDocumentByIds(index,type,Arrays.asList(ids.split(",")));
    }

    /**
     * 从索引库中删除多个文档数据
     *
     * @param index 索引库名称
     * @param type 文档类型名称
     * @param ids 要删除的文档ID集
     * @return 被删除的文档数目
     */
    default long deleteDocumentByIds(String index, String type, Iterable<String> ids){
        long deleteCount=0L;
        for (String docId :
                ids) {
            deleteCount += deleteDocumentById(index,type,docId);
        }
        return deleteCount;
    }

    /**
     * 更新索引库中的文档数据
     *
     * @param index 索引库名称
     * @param type 文档类型名称
     * @param document 文档数据
     * @param isUpsert 指示当索引库中如果不存在该文档是否需要插入 true=插入 false=不插入，默认为true
     * @return 被更新/插入的文档数
     */
    long updateDocument(String index, String type, Indexable document, boolean isUpsert);

    /**
     * 更新索引库中多个文档数据
     *
     * @param index 索引库名称
     * @param type 文档类型名称
     * @param documents 文档数据集
     * @param isUpsert  指示当索引库中如果不存在该文档是否需要插入 true=插入 false=不插入，默认为true
     * @return  被更新/插入的文档数
     */
    default long updateDocuments(String index, String type, Iterable<Indexable> documents, boolean isUpsert){
        long updateCount=0L;
        for (Indexable document :
                documents) {
            updateCount += this.updateDocument(index, type, document,isUpsert);
        }
        return updateCount;
    }

    /**
     * 从索引库中根据ID获取单条文档数据
     *
     * @param index 索引库名称
     * @param type 文档类型名称
     * @param id 文档ID
     * @param rtnFields 需要返回的文档数据字段数组，可选参数，如不提供表示返回全部字段
     * @return 文档数据
     */
    Map<String, Object> fetchDocumentById(String index, String type, String id, String... rtnFields);

    /**
     *  从索引库中根据ID集获取多条文档数据
     *
     * @param index 索引库名称
     * @param type 文档类型名称
     * @param ids 文档ID集字符串，ID之间以逗号分隔
     * @param rtnFields  需要返回的文档数据字段数组，可选参数，如不提供表示返回全部字段
     * @return 文档ID与对应文档数据的映射
     */
    default Map<String, Object> fetchDocumentByIds(String index, String type, String ids, String... rtnFields){
        return this.fetchDocumentByIds(index,type, Arrays.asList(ids.split(",")),rtnFields);
    }

    /**
     * 根据ID集合从索引库中获取相应的文档数据
     *
     * @param index 索引库名称
     * @param type 文档类型名称
     * @param ids 文档ID集
     * @param rtnFields  需要返回的文档数据字段数组，可选参数，如不提供表示返回全部字段
     * @return 文档ID与对应文档数据的映射
     */
    default Map<String, Object> fetchDocumentByIds(String index, String type, Iterable<String> ids, String... rtnFields){
        Map<String,Object> docs = new TreeMap<>();
        for (String docId :
                ids) {
            Map<String, Object> documentObject = this.fetchDocumentById(index,type,docId,rtnFields);
            if(Objects.nonNull(documentObject) && !documentObject.isEmpty()){
                docs.put(docId,documentObject);
            }
        }
        return docs;
    }

    /**
     * 按分页方式进行查询
     *
     * <pre>
     * 分页参数对象应该包括参数项：
     *  startPage ：起始页，如果不传或值不合法情况下下，使用默认值0，即第1页
     *  pageSize ：每页条数，如果不传或值不合法情况下下，使用默认值20
     * </pre>
     *
     * @param queryParams 分页查询参数对象
     * @return 分页数据对象
     */
    SearchResultPage search(PageQueryParams queryParams);

    /**
     * 按列表方式（不分页）进行查询
     *
     * <pre>
     * 列表参数对象应该包括参数项：
     *  size ：返回的数据条数，如果不传或值不合法情况下下，使用默认值10
     * </pre>
     *
     * @param queryParams 列表参数对象
     * @return 列表数据对象
     */
    List<Map<String, Object>> search(ListQueryParams queryParams);

    /**
     * 设置文档类型的字段类型定义
     *
     * @param index 索引库名称
     * @param type 文档类型名称
     * @param mapping 类型定义字符串
     */
    void setMapping(String index, String type, String mapping);

    /**
     * 根据URI的参数字符串进行查询
     *
     * @param index 索引库名称
     * @param type 文档类型名称
     * @param queryString httpRequest里的uri query string的参数字符串,搜索条件参数名q
     *                    queryString值应该为已经经过URLDecoder处理过的字符串。例如：
     *                    "q=规范&num=30&sort="
     * @return 文档数据列表s
     */
    List<Map<String, Object>> search(String index, String type, String queryString);

}
